/** This program is free software: you can redistribute it and/or modify
    it under the terms of the Lesser GNU General Public License as published by
    the Free Software Foundation, either version 2.1 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    Lesser GNU General Public License for more details.

    You should have received a copy of the Lesser GNU General Public License
    along with this program. If not, see < http://www.gnu.org/licenses/>.
*/

package dz.borconlib.bot;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import dz.borconlib.BORcon;
import dz.borconlib.PlayerStatus;
import dz.borconlib.events.BOAbstractEvent;
import dz.borconlib.events.DeadEvent;
import dz.borconlib.events.ExitLevelEvent;
import dz.borconlib.events.InitGameEvent;
import dz.borconlib.events.JoinEvent;
import dz.borconlib.events.KillEvent;
import dz.borconlib.events.QuitEvent;
import dz.borconlib.events.SayEvent;
import dz.borconlib.events.ShutdownGame;
import dz.borconlib.events.WeaponEvent;
import dz.borconlib.logger.LoggerListener;

/***
 * 
 * @author Mourad TOUMI
 */
public class BORconBot extends BORcon implements Runnable {
	
	private Map<String,String> fNickNames;
	
	private Map<String,Integer> fBadWordWarnings;
	
	private Map<String,Integer> fMaxPingWarnings;
	
	private List<String> fBadWords;
	
	private Map<String, PlayerStatus> fPlayersStatus;
	
	private String fNextGameType = "11";
	private Map<String, Integer> fVoteForGameType;
	private Map<String,String> fPlayersVotes;

	private Map<String, String> fAvailableGameTypes;
	
	
	private Integer fMaxBadWordWarningCount;
	private Integer fMaxPing;
	private boolean fKickMaxPing;
	private Integer fMaxMaxPingWarningCount;
	
	private List<String> fNotAllowedAtack;
	private Integer fNotAllowedAttackMaxWarning = 0;
	private String fNotAllowedAttackKickMessage = "Attack not autorized  !";

	private LoggerListener fLoggerListener;
	
	private boolean fVoteActivated = true;
	
    private String fWelcomeMessage = "Welcome to ^2Black^7Ops^0-^1DZ ^7server";
	
	private String fRules = "";
	
	private Integer fGamePlayVoteInterval = 3;
	private Integer fGamePlayVoteCounter = 0;
	
	private String fBadLangageMessageWarningTemplate = "^1: Bad language !! Warning ${COUNT}/${MAX}";
	private String fBadLangageKickMessage = "Kicked for bad language !";
	private String fHighPingKickMessage = "Sorry, too high ping !";
	
	public BORconBot(final int pPort, final String pAddress, final String pPass, String pLogUrl) {
		super(pPort, pAddress, pPass);
		fLoggerListener = new LoggerListener(pLogUrl, 0);
		
		fMaxBadWordWarningCount = 2;
		fMaxPing = 165;
		fMaxMaxPingWarningCount = 6;
		
		initLists();
	}
	
	@Override 
	public boolean init() {
		return super.init();
	}

	private void initLists() {	
		fNickNames = new HashMap<String, String>();	
		fBadWords = new ArrayList<String>();
		fBadWordWarnings = new HashMap<String, Integer>();
		fMaxPingWarnings = new HashMap<String, Integer>();
		fPlayersStatus = new HashMap<String, PlayerStatus>();
		
		fVoteForGameType = new HashMap<String, Integer>();
		
		fAvailableGameTypes = new HashMap<String,String>();
		
		fNotAllowedAtack = new ArrayList<String>();
		
		fVoteActivated = true;
		
		fPlayersVotes = new HashMap<String,String>();
		
	}
	
	/***
	 * 
	 * @param pMaxBadWordWarningCount Nombre maximum d'avertissement avec l'exclusion pour mauvais langage
	 */
	public void setMaxBadWordWarningCount(Integer pMaxBadWordWarningCount) {
		fMaxBadWordWarningCount = pMaxBadWordWarningCount;
	}

	/***
	 * 
	 * @return Nombre maximum d'avertissement avec l'exclusion pour mauvais langage
	 */
	public Integer getMaxBadWordWarningCount() {
		return fMaxBadWordWarningCount;
	}
	
	/***
	 * Qualifier un mot donné comme vulgaire
	 * @param pBadWord Mot à ajouter dans la liste des mots vulgaires
	 */
	public void addBadWord(final String pBadWord) {
		if (!fBadWords.contains(pBadWord)) {
			fBadWords.add(pBadWord);
		}
	}
	
	/***
	 *  
	 * @param pOldBadWord Mot à supprimer de la liste des mots vulgaires
	 */
	public void removeBadWord(final String pOldBadWord) {
		if (!fBadWords.contains(pOldBadWord)) {
			fBadWords.remove(pOldBadWord);
		}
	}

	/***
	 * 
	 * @param fMaxMaxPingWarningCount Nombre maximum d'avertissement avec l'exclusion pour ping élevé
	 */
	public void setMaxMaxPingWarningCount(Integer fMaxMaxPingWarningCount) {
		this.fMaxMaxPingWarningCount = fMaxMaxPingWarningCount;
	}

	/***
	 * 
	 * @return Nombre maximum d'avertissement avec l'exclusion pour ping élevé
	 */
	public Integer getMaxMaxPingWarningCount() {
		return fMaxMaxPingWarningCount;
	}

	/***
	 * Activer/Désactiver l'exclusion pour ping élevé
	 * @param fKickMaxPing true pour activer l'exclusion pour ping élevé, false pour désactiver
	 */
	public void setKickMaxPing(boolean fKickMaxPing) {
		this.fKickMaxPing = fKickMaxPing;
	}

	/***
	 * 
	 * @return true si l'exclusion pour ping élevé est activée, false sinon
	 */
	public boolean isKickMaxPing() {
		return fKickMaxPing;
	}

	/***
	 * Fixer le ping max autorisé sur le serveur
	 * @param fMaxPing ping max autorisé sur le serveur
	 */
	public void setMaxPing(Integer fMaxPing) {
		this.fMaxPing = fMaxPing;
	}

	/***
	 * Récupère le ping max autorisé sur le serveur
	 * @return ping max autorisé sur le serveur
	 */
	public Integer getMaxPing() {
		return fMaxPing;
	}

	/***
	 * 
	 * @return List des avertissement assignées aux joueurs dans la partie en cours
	 */
	public Map<String,Integer> getWarnings() {
		return fBadWordWarnings;
	}

	/***
	 * 
	 * @return Liste des noms Steam des joueurs dans la partie en cours
	 */
	public Map<String,String> getNickNames() {
		return fNickNames;
	}
	
	private void processNewGameEvent(final BOAbstractEvent pGameEvent) {	
		
		if (pGameEvent.getType().equals(InitGameEvent.TYPE)) {
			
		} else if (pGameEvent.getType().equals(ExitLevelEvent.TYPE)) {
			fGamePlayVoteCounter = 0;
			clear();
			
		} else if (pGameEvent.getType().equals(ShutdownGame.TYPE)) {
			initLists();
			fGamePlayVoteCounter = 0;
	
		} else if (pGameEvent.getType().equals(JoinEvent.TYPE)) {
			fPlayersStatus.putAll(updateStatus());
			fMaxPingWarnings.put(pGameEvent.getPlayerId(),0);
			fBadWordWarnings.put(pGameEvent.getPlayerId(),0);
			
			sendMessage(fPlayersStatus.get(pGameEvent.getPlayerId()).getNum(), fWelcomeMessage +" Powered by ^2DZBORcon ^1BOT");
			
		} else if (pGameEvent.getType().equals(KillEvent.TYPE)) {
			if (fNotAllowedAtack.contains(((KillEvent)pGameEvent).getAttack())) {
				kick(fNickNames.get(pGameEvent.getPlayerId()), fNotAllowedAttackKickMessage);
			}
			
		} else if (pGameEvent.getType().equals(DeadEvent.TYPE)) {
			//TODO a traiter, mais entre nous, si il s'est fait tue si qu il etait naze :D
			
		} else if (pGameEvent.getType().equals(QuitEvent.TYPE)) {
			fPlayersStatus.remove(pGameEvent.getPlayerId());
			fBadWordWarnings.remove(pGameEvent.getPlayerId());
			fMaxPingWarnings.remove(pGameEvent.getPlayerId());
			fNickNames.remove(pGameEvent.getPlayerId());
			
		} else if (pGameEvent.getType().equals(SayEvent.Type)) {
			
			//si c'est un vote :
			final String msg = ((SayEvent)pGameEvent).getMessage();
			if (msg.matches(".vote [0-9]*")) {
				addVoteForGameType(pGameEvent.getPlayerId(), msg.split("\\s+")[1]);
			}
			
			//si le text contien un mot vulgaire
			for (String badWord : fBadWords) {
				if (((SayEvent)pGameEvent).getMessage().contains(badWord)) {
					if (fBadWordWarnings.get(pGameEvent.getPlayerId()) == null) {
						fBadWordWarnings.put(pGameEvent.getPlayerId(), 1);
					} else {
						fBadWordWarnings.put(pGameEvent.getPlayerId(), fBadWordWarnings.get(pGameEvent.getPlayerId()) + 1);
						final String count = Integer.toString(fBadWordWarnings.get(pGameEvent.getPlayerId()));
						final String max = Integer.toString(fMaxBadWordWarningCount);
						sendMessage("@^2"+ fNickNames.get(pGameEvent.getPlayerId())+" "+fBadLangageMessageWarningTemplate.replaceFirst("${COUNT}",count).replaceFirst("${MAX}", max));
					}

					if (fBadWordWarnings.get(pGameEvent.getPlayerId()) >= fMaxBadWordWarningCount) {	
						
						kick(fNickNames.get(pGameEvent.getPlayerId()), fBadLangageKickMessage);
					}	
				}
			}
			
		} else if (pGameEvent.getType().equals(WeaponEvent.TYPE)) {
			//TODO � traiter .... pour le moment je ne sais pas trop :D
		}
	}
	
	private void addVoteForGameType(final String pPlayerId, final String pGameType) {
		if (fAvailableGameTypes.get(pGameType) != null) {
			if (fVoteForGameType.get(pGameType) != null) {
				if (fPlayersVotes.get(pPlayerId) != null) {
					fVoteForGameType.put(fPlayersVotes.get(pPlayerId), fVoteForGameType.get(fPlayersVotes.get(pPlayerId))-1);
					fPlayersVotes.put(pPlayerId, pGameType);	
				}
				fVoteForGameType.put(pGameType, fVoteForGameType.get(pGameType)+1);
				
			} else
			{
				if (fPlayersVotes.get(pPlayerId) != null) {
					fVoteForGameType.put(fPlayersVotes.get(pPlayerId), fVoteForGameType.get(fPlayersVotes.get(pPlayerId))-1);
					fPlayersVotes.put(pPlayerId, pGameType);	
				}
				fVoteForGameType.put(pGameType, 1);
				fPlayersVotes.put(pPlayerId, pGameType);
			}
			
			//mettre � jour le type de jeu suivant
			Integer max = 0;
			
			for (Map.Entry<String, Integer> e : fVoteForGameType.entrySet()) {
				if (e.getValue() > max) {
					max = e.getValue();
					fNextGameType = e.getKey();
					setGamePlay(fNextGameType);
				}
			}
			
			sendMessage("Vote passed ! ^2"+pGameType+" ^1"+max);
		} else {
			sendMessage(fPlayersStatus.get(pPlayerId).getGuid(),"Invalid vote, please try again !");
		}
	}
	
	private void clear() {
		fMaxPingWarnings.clear();
		fBadWordWarnings.clear();
	}
	
	private boolean overMaxPing(final PlayerStatus pPlayerStatus) {
		return (pPlayerStatus.getPing() > fMaxPing && pPlayerStatus.getPing() < 480);
	}
	
	@Override
	public String sendCommande(final String pCommande) {
		return super.sendCommande(pCommande);
	}
	
	/***
	 * Afface les régles et réstrictions liées aux armes
	 */
	public void clearRules() {
		fNotAllowedAtack.clear();
	}
	
	/***
	 * Ajout d'une réstriction sur une attaque
	 * @param pAttack nom de l'attaque
	 */
	public void addNotAllowedAtack(final String pAttack) {
		fNotAllowedAtack.add(pAttack);
	}

	@Override
	public void run() {
		init();
		while(true) {
			//recupere la liste des joueurs actuels
			fPlayersStatus = updateStatus();
			
			if (fVoteActivated && fGamePlayVoteCounter++ > fGamePlayVoteInterval) {
				showVoteForGameTypeOptions();
				//fVoteActivated = false;
				fGamePlayVoteCounter = 0;
			}
			// Tester depassement ping max et kick dans ce cas
			for (Map.Entry<String, PlayerStatus> e : fPlayersStatus.entrySet()) {
				fNickNames.put(e.getKey(), e.getValue().getName());
				
				if (overMaxPing(e.getValue())) {
					
					if (!fMaxPingWarnings.containsKey(e.getKey())) {
						fMaxPingWarnings.put(e.getKey(), 0);
					}
					
					fMaxPingWarnings.put(e.getKey(), fMaxPingWarnings.get(e.getKey()) + 1);
					
					if (fMaxPingWarnings.get(e.getKey()) > fMaxMaxPingWarningCount) {
						kick(fNickNames.get(e.getKey()),fHighPingKickMessage);
					}
				}
			}
			
			//capture des evenements du jeu depuis le LoggerListener
			List<BOAbstractEvent> listGameEvents;
			try {
				listGameEvents = fLoggerListener.getGameEvents();
				
				for (BOAbstractEvent event : listGameEvents) {
					processNewGameEvent(event);
				}
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				System.out.println(e1.getMessage());
				e1.printStackTrace();
			}
		}
	}
	
	/***
	 * Definit la liste de type de jeu disponoble pour le vote
	 * @param pAvailablesGameType Collection de Types de jeu (ID_PlayList, NomPersonalisé)
	 */
	public void setAvailablesGameTypes(final Map<String,String> pAvailablesGameType) {
		fAvailableGameTypes = pAvailablesGameType;
	}
	
	private void showVoteForGameTypeOptions() {	
		String voteMsg = "^3Vote for the next Game Type";
	
		sendMessage(voteMsg);
		
		for (Map.Entry<String, String> e : fAvailableGameTypes.entrySet()){
			voteMsg = "^2vote "+e.getKey( )+" ^7for ^5"+e.getValue(); 
			sendMessage(voteMsg);
		}
		
	}
	
	/***
	 * Définit le message de bienvenue
	 * @param pWelcomeMessage Message de bienvenue
	 */
	public void setWelcomeMessage(final String pWelcomeMessage) {
		fWelcomeMessage = pWelcomeMessage;
	}

	/***
	 * 
	 * @param fGamePlayVoteInterval
	 */
	public void setGamePlayVoteInterval(Integer fGamePlayVoteInterval) {
		this.fGamePlayVoteInterval = fGamePlayVoteInterval;
	}
	
	private void setGamePlay(final String pGamePlay) {
		sendCommande("setadmindvar playlist "+pGamePlay);
	}

	public void setRulesMessage(String fRules) {
		this.fRules = fRules;
	}

	/***
	 * 
	 * @return Text abrégé du réglement
	 */
	public String getRules() {
		return fRules;
	}
	
	public void setBadLangageMessageWarningTemplate(final String pBadLangageMessageWarningTemplate) {
		fBadLangageMessageWarningTemplate = pBadLangageMessageWarningTemplate;
	}

	public void setNotAllowedAttackMaxWarning(
			Integer fNotAllowedAttackMaxWarning) {
		this.fNotAllowedAttackMaxWarning = fNotAllowedAttackMaxWarning;
	}

}
